#pragma once
#include <memory>

#include "ShaderProgram.h"

class FShaderProgram;

class FLightBase
{
public:
	virtual ~FLightBase() = default;

	FLightBase* SetUniformStructName(const std::string& Name)
	{
		ShaderUniformName = Name;
		return this;
	}

	const std::string& GetUniformStructName()const { return ShaderUniformName; }  // NOLINT(modernize-use-nodiscard)

	void SetLightBaseValue(const glm::vec3& InAmbient, const glm::vec3& InDiffuse, const glm::vec3& InSpecular)
	{
		Ambient = InAmbient;
		Diffuse = InDiffuse;
		Specular = InSpecular;
	}

	void ApplyValueToShader(const std::string& StructName, const std::shared_ptr<FShaderProgram>& ShaderProgram)
	{
		ShaderProgram->Use();
		this->ApplyValue(StructName, ShaderProgram);
	}

protected:

	virtual void ApplyValue(const std::string& StructName, const std::shared_ptr<FShaderProgram>& ShaderProgram)
	{
		if (ShaderProgram->GetLightingModel() == ELightingModel::Diffuse)
		{
			ShaderProgram->SetVec3(StructName + ".ambient", Ambient);
			ShaderProgram->SetVec3(StructName + ".diffuse", Diffuse);
		}
		else
		{
			ShaderProgram->SetVec3(StructName + ".ambient", Ambient);
			ShaderProgram->SetVec3(StructName + ".diffuse", Diffuse);
			ShaderProgram->SetVec3(StructName + ".specular", Specular);
		}
	}

private:

	// 环境光
	glm::vec3 Ambient = {};
	// 漫反射
	glm::vec3 Diffuse = {};
	// 镜面反射
	glm::vec3 Specular = {};

	std::string ShaderUniformName;
};


// 定向光
struct FDirectionLight
	: public FLightBase
{
protected:

	void ApplyValue(const std::string& StructName, const std::shared_ptr<FShaderProgram>& ShaderProgram) override
	{
		FLightBase::ApplyValue(StructName, ShaderProgram);

		ShaderProgram->SetVec3(StructName + ".direction", Direction);
	}

public:

	FDirectionLight* SetDirection(const glm::vec3 InDirection)
	{
		Direction = InDirection;
		return this;
	}

private:

	glm::vec3 Direction = {};
};

// 点光源
struct FPointLight
	: public FLightBase
{
protected:

	void ApplyValue(const std::string& StructName, const std::shared_ptr<FShaderProgram>& ShaderProgram) override
	{
		FLightBase::ApplyValue(StructName, ShaderProgram);

		ShaderProgram->SetVec3(StructName + ".position", Position);
		ShaderProgram->SetFloat(StructName + ".constant", Constant);
		ShaderProgram->SetFloat(StructName + ".linear", Linear);
		ShaderProgram->SetFloat(StructName + ".quadratic", Quadratic);
	}

public:

	/**
	 * \brief 设置点光源的衰减
	 * \param InConstant
	 * \param InLinear
	 * \param InQuadratic
	 * \return *this 方便连续调用
	 */
	FPointLight* SetPointAttenuationValue(const float InConstant, const float InLinear, const float InQuadratic)
	{
		Constant = InConstant;
		Linear = InLinear;
		Quadratic = InQuadratic;
		return this;
	}

	FPointLight* SetPosition(const glm::vec3 InPosition)
	{
		Position = InPosition;
		return this;
	}

	glm::vec3 GetPosition()const { return  Position; }
private:

	glm::vec3 Position = {};

	// 常数项
	float Constant = 1.f;
	// 线性衰减
	float Linear = 0.f;
	// 二次衰减 
	float Quadratic = 0.f;
};

// 聚光灯
struct FSpotLight
	: public FPointLight
{
public:


	/**
	 * \brief 设置剪裁的角度
	 * \param InCutOff  内圆锥切光角  角度
	 * \param InOuterCutOff 外圆锥切光角  角度
	 * \return *this 方便连续调用
	 */
	FSpotLight* SetCutOffAngle(const float InCutOff, const float InOuterCutOff)
	{
		CutOff = glm::cos(glm::radians(InCutOff));
		OuterCutOff = glm::cos(glm::radians(InOuterCutOff));
		return this;
	}

	FSpotLight* SetDirection(const glm::vec3& InDirection)
	{
		Direction = InDirection;
		return this;
	}

protected:

	void ApplyValue(const std::string& StructName, const std::shared_ptr<FShaderProgram>& ShaderProgram) override
	{
		FPointLight::ApplyValue(StructName, ShaderProgram);

		ShaderProgram->SetVec3(StructName + ".direction", Direction);
		ShaderProgram->SetFloat(StructName + ".cutOff", CutOff);
		ShaderProgram->SetFloat(StructName + ".outerCutOff", OuterCutOff);
	}

private:

	glm::vec3  Direction = {};
	float CutOff = 1;
	float OuterCutOff = 1;
};



